package com.ming_za.odupdaterserver.core.config.type;

import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;
import java.util.function.*;

/**
 * 配置文件值类型抽象类
 * <p>配置文件主要存储的是字符串类型的数据</p>
 * <p>当读取、修改配置值时，由该类的子类提供字符串和对象之间的转换</p>
 * <p>如果需要创建一个新的类型，继承该类并实现{@link #valueToObject(String)}、{@link #objectToValue(Object)}即可</p>
 * @param <T> 对象类型
 */
public abstract class ValueType <T>{

    @Getter
    private final String key;

    private final T defaultVale;

    /**
     * 监听器，用于对该value修改后监听
     */
    private final List<Consumer<T>> setListener = new ArrayList<>();

    /**
     * 配置文件值缓存，以对象形式，且提供set、get方法
     */
    @Getter @Setter
    private T value;

    private final BiConsumer<String,String> setter;
    private final Function<String,String> getter;

    /**
     * 调用该构造函数同时调用{@link #loadValue()}从存储中加载value
     * @param key 配置文件对应的key，当调用{@link }
     * @param getter 该方法获取配置文件值（字符串）
     *               <p>传入key，返回value(可为null)</p>
     * @param setter 该方法设置配置文件值（字符串）
     *               <p>传入key、value(可能为null)</p>
     */
    public ValueType(String key, Function<String,String> getter, BiConsumer<String,String> setter) {
        this.key = key;
        this.setter = setter;
        this.getter = getter;
        this.defaultVale = null;
        loadValue();
    }

    /**
     * 调用该构造函数同时调用{@link #loadValue()}从存储中加载value
     * @param key 配置文件对应的key，当调用{@link }
     * @param defaultVale 默认值
     * @param getter 该方法获取配置文件值（字符串）
     *               <p>传入key，返回value(可为null)</p>
     * @param setter 该方法设置配置文件值（字符串）
     *               <p>传入key、value(可能为null)</p>
     */
    public ValueType(String key,T defaultVale, Function<String,String> getter, BiConsumer<String,String> setter) {
        this.key = key;
        this.setter = setter;
        this.getter = getter;
        this.defaultVale = defaultVale;
        loadValue();
    }

    /**
     * 将值保存到配置文件中
     * <p>调用此方法将{@link #value}转换为字符串类型，再交由{@link #setter}进行存储</p>
     */
    public void saveValue(){
        setter.accept(this.key,objectToValue(this.value));
    }

    /**
     * 详细说明见{@link #saveValue()}
     * @param value 先将{@link #value}设置为传入参数，再交由{@link #setter}进行存储
     */
    public void saveValue(T value){
        this.value = value;
        setter.accept(this.key,objectToValue(this.value));
        for(Consumer<T> setListener : this.setListener){
            setListener.accept(this.value);
        }
    }

    /**
     * 加载value，一般用于初始化或重载配置
     * <p>该方法通过{@link #getter}获取到字符串类型文件，再转换为对象并赋值给{@link #value}</p>
     * <p>如果获取到的值为null，则调用{@link #valueIsNull()}</p>
     * <p>如果{@link #valueIsNull()}返回的值不为null，则自动调用{@link #saveValue(Object)}存储</p>
     */
    public void loadValue(){

        T c = valueToObject(getter.apply(this.key));

        if (c == null){
            c = valueIsNull();
            if (c != null){
                saveValue(c);
            }
        }

        this.value = c;
    }

    /**
     * 值转换为对象，用于兼容各种配置文件值类型
     * @param value 从配置文件中读取到的值,可能为null
     * @return 转换完成后的对象
     */
    protected abstract T valueToObject(String value);

    /**
     * 对象转换为值，用于兼容各种配置文件值类型
     * @param value 对象,可能为null
     * @return 对象转换成的字符串
     */
    protected abstract String objectToValue(T value);

    /**
     * 如果需要在获取不到值时进行额外操作，可以重写该方法
     * @return 值，返回的值（非null的情况下）将赋给{@link #value}并调用存储方法，详情见{@link #loadValue()}
     */
    protected T valueIsNull(){
        return defaultVale;
    };

    /**
     * 绑定监听，当value修改时调用监听消费者
     * @param listener 监听消费者实现类
     *                 <p>T为更新后的值</p>
     */
    public void bindListener(Consumer<T> listener){
        setListener.add(listener);
    }

    /**
     * 取消绑定
     * @param listener 监听消费者实现类
     *                 <p>T为更新后的值</p>
     */
    public void unbindListener(Consumer<T> listener){
        setListener.remove(listener);
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}
